home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / tcl / tcl67.lha / tcl6.7 / tclUnixUtil.c < prev    next >
C/C++ Source or Header  |  1993-01-08  |  28KB  |  1,011 lines

  1. /* 
  2.  * tclUnixUtil.c --
  3.  *
  4.  *    This file contains a collection of utility procedures that
  5.  *    are present in the Tcl's UNIX core but not in the generic
  6.  *    core.  For example, they do file manipulation and process
  7.  *    manipulation.
  8.  *
  9.  *    The Tcl_Fork and Tcl_WaitPids procedures are based on code
  10.  *    contributed by Karl Lehenbauer, Mark Diekhans and Peter
  11.  *    da Silva.
  12.  *
  13.  * Copyright 1991 Regents of the University of California
  14.  * Permission to use, copy, modify, and distribute this
  15.  * software and its documentation for any purpose and without
  16.  * fee is hereby granted, provided that this copyright
  17.  * notice appears in all copies.  The University of California
  18.  * makes no representations about the suitability of this
  19.  * software for any purpose.  It is provided "as is" without
  20.  * express or implied warranty.
  21.  */
  22.  
  23. #ifndef lint
  24. static char rcsid[] = "$Header: /user6/ouster/tcl/RCS/tclUnixUtil.c,v 1.19 93/01/08 08:41:00 ouster Exp $ SPRITE (Berkeley)";
  25. #endif /* not lint */
  26.  
  27. #include "tclInt.h"
  28. #include "tclUnix.h"
  29.  
  30. /*
  31.  * Data structures of the following type are used by Tcl_Fork and
  32.  * Tcl_WaitPids to keep track of child processes.
  33.  */
  34.  
  35. typedef struct {
  36.     int pid;            /* Process id of child. */
  37.     WAIT_STATUS_TYPE status;    /* Status returned when child exited or
  38.                  * suspended. */
  39.     int flags;            /* Various flag bits;  see below for
  40.                  * definitions. */
  41. } WaitInfo;
  42.  
  43. /*
  44.  * Flag bits in WaitInfo structures:
  45.  *
  46.  * WI_READY -            Non-zero means process has exited or
  47.  *                suspended since it was forked or last
  48.  *                returned by Tcl_WaitPids.
  49.  * WI_DETACHED -        Non-zero means no-one cares about the
  50.  *                process anymore.  Ignore it until it
  51.  *                exits, then forget about it.
  52.  */
  53.  
  54. #define WI_READY    1
  55. #define WI_DETACHED    2
  56.  
  57. static WaitInfo *waitTable = NULL;
  58. static int waitTableSize = 0;    /* Total number of entries available in
  59.                  * waitTable. */
  60. static int waitTableUsed = 0;    /* Number of entries in waitTable that
  61.                  * are actually in use right now.  Active
  62.                  * entries are always at the beginning
  63.                  * of the table. */
  64. #define WAIT_TABLE_GROW_BY 4
  65.  
  66. /*
  67.  *----------------------------------------------------------------------
  68.  *
  69.  * Tcl_EvalFile --
  70.  *
  71.  *    Read in a file and process the entire file as one gigantic
  72.  *    Tcl command.
  73.  *
  74.  * Results:
  75.  *    A standard Tcl result, which is either the result of executing
  76.  *    the file or an error indicating why the file couldn't be read.
  77.  *
  78.  * Side effects:
  79.  *    Depends on the commands in the file.
  80.  *
  81.  *----------------------------------------------------------------------
  82.  */
  83.  
  84. int
  85. Tcl_EvalFile(interp, fileName)
  86.     Tcl_Interp *interp;        /* Interpreter in which to process file. */
  87.     char *fileName;        /* Name of file to process.  Tilde-substitution
  88.                  * will be performed on this name. */
  89. {
  90.     int fileId, result;
  91.     struct stat statBuf;
  92.     char *cmdBuffer, *end, *oldScriptFile;
  93.     Interp *iPtr = (Interp *) interp;
  94.  
  95.     oldScriptFile = iPtr->scriptFile;
  96.     iPtr->scriptFile = fileName;
  97.     fileName = Tcl_TildeSubst(interp, fileName);
  98.     if (fileName == NULL) {
  99.     goto error;
  100.     }
  101.     fileId = open(fileName, O_RDONLY, 0);
  102.     if (fileId < 0) {
  103.     Tcl_AppendResult(interp, "couldn't read file \"", fileName,
  104.         "\": ", Tcl_UnixError(interp), (char *) NULL);
  105.     goto error;
  106.     }
  107.     if (fstat(fileId, &statBuf) == -1) {
  108.     Tcl_AppendResult(interp, "couldn't stat file \"", fileName,
  109.         "\": ", Tcl_UnixError(interp), (char *) NULL);
  110.     close(fileId);
  111.     goto error;
  112.     }
  113.     cmdBuffer = (char *) ckalloc((unsigned) statBuf.st_size+1);
  114.     if (read(fileId, cmdBuffer, (int) statBuf.st_size) != statBuf.st_size) {
  115.     Tcl_AppendResult(interp, "error in reading file \"", fileName,
  116.         "\": ", Tcl_UnixError(interp), (char *) NULL);
  117.     close(fileId);
  118.     ckfree(cmdBuffer);
  119.     goto error;
  120.     }
  121.     if (close(fileId) != 0) {
  122.     Tcl_AppendResult(interp, "error closing file \"", fileName,
  123.         "\": ", Tcl_UnixError(interp), (char *) NULL);
  124.     ckfree(cmdBuffer);
  125.     goto error;
  126.     }
  127.     cmdBuffer[statBuf.st_size] = 0;
  128.     result = Tcl_Eval(interp, cmdBuffer, 0, &end);
  129.     if (result == TCL_RETURN) {
  130.     result = TCL_OK;
  131.     }
  132.     if (result == TCL_ERROR) {
  133.     char msg[200];
  134.  
  135.     /*
  136.      * Record information telling where the error occurred.
  137.      */
  138.  
  139.     sprintf(msg, "\n    (file \"%.150s\" line %d)", fileName,
  140.         interp->errorLine);
  141.     Tcl_AddErrorInfo(interp, msg);
  142.     }
  143.     ckfree(cmdBuffer);
  144.     iPtr->scriptFile = oldScriptFile;
  145.     return result;
  146.  
  147.     error:
  148.     iPtr->scriptFile = oldScriptFile;
  149.     return TCL_ERROR;
  150. }
  151.  
  152. /*
  153.  *----------------------------------------------------------------------
  154.  *
  155.  * Tcl_Fork --
  156.  *
  157.  *    Create a new process using the vfork system call, and keep
  158.  *    track of it for "safe" waiting with Tcl_WaitPids.
  159.  *
  160.  * Results:
  161.  *    The return value is the value returned by the vfork system
  162.  *    call (0 means child, > 0 means parent (value is child id),
  163.  *    < 0 means error).
  164.  *
  165.  * Side effects:
  166.  *    A new process is created, and an entry is added to an internal
  167.  *    table of child processes if the process is created successfully.
  168.  *
  169.  *----------------------------------------------------------------------
  170.  */
  171.  
  172. int
  173. Tcl_Fork()
  174. {
  175.     WaitInfo *waitPtr;
  176.     pid_t pid;
  177.  
  178.     /*
  179.      * Disable SIGPIPE signals:  if they were allowed, this process
  180.      * might go away unexpectedly if children misbehave.  This code
  181.      * can potentially interfere with other application code that
  182.      * expects to handle SIGPIPEs;  what's really needed is an
  183.      * arbiter for signals to allow them to be "shared".
  184.      */
  185.  
  186.     if (waitTable == NULL) {
  187.     (void) signal(SIGPIPE, SIG_IGN);
  188.     }
  189.  
  190.     /*
  191.      * Enlarge the wait table if there isn't enough space for a new
  192.      * entry.
  193.      */
  194.  
  195.     if (waitTableUsed == waitTableSize) {
  196.     int newSize;
  197.     WaitInfo *newWaitTable;
  198.  
  199.     newSize = waitTableSize + WAIT_TABLE_GROW_BY;
  200.     newWaitTable = (WaitInfo *) ckalloc((unsigned)
  201.         (newSize * sizeof(WaitInfo)));
  202.     memcpy((VOID *) newWaitTable, (VOID *) waitTable,
  203.         (waitTableSize * sizeof(WaitInfo)));
  204.     if (waitTable != NULL) {
  205.         ckfree((char *) waitTable);
  206.     }
  207.     waitTable = newWaitTable;
  208.     waitTableSize = newSize;
  209.     }
  210.  
  211.     /*
  212.      * Make a new process and enter it into the table if the fork
  213.      * is successful.
  214.      */
  215.  
  216.     waitPtr = &waitTable[waitTableUsed];
  217.     pid = fork();
  218.     if (pid > 0) {
  219.     waitPtr->pid = pid;
  220.     waitPtr->flags = 0;
  221.     waitTableUsed++;
  222.     }
  223.     return pid;
  224. }
  225.  
  226. /*
  227.  *----------------------------------------------------------------------
  228.  *
  229.  * Tcl_WaitPids --
  230.  *
  231.  *    This procedure is used to wait for one or more processes created
  232.  *    by Tcl_Fork to exit or suspend.  It records information about
  233.  *    all processes that exit or suspend, even those not waited for,
  234.  *    so that later waits for them will be able to get the status
  235.  *    information.
  236.  *
  237.  * Results:
  238.  *    -1 is returned if there is an error in the wait kernel call.
  239.  *    Otherwise the pid of an exited/suspended process from *pidPtr
  240.  *    is returned and *statusPtr is set to the status value returned
  241.  *    by the wait kernel call.
  242.  *
  243.  * Side effects:
  244.  *    Doesn't return until one of the pids at *pidPtr exits or suspends.
  245.  *
  246.  *----------------------------------------------------------------------
  247.  */
  248.  
  249. int
  250. Tcl_WaitPids(numPids, pidPtr, statusPtr)
  251.     int numPids;        /* Number of pids to wait on:  gives size
  252.                  * of array pointed to by pidPtr. */
  253.     int *pidPtr;        /* Pids to wait on:  return when one of
  254.                  * these processes exits or suspends. */
  255.     int *statusPtr;        /* Wait status is returned here. */
  256. {
  257.     int i, count, pid;
  258.     register WaitInfo *waitPtr;
  259.     int anyProcesses;
  260.     WAIT_STATUS_TYPE status;
  261.  
  262.     while (1) {
  263.     /*
  264.      * Scan the table of child processes to see if one of the
  265.      * specified children has already exited or suspended.  If so,
  266.      * remove it from the table and return its status.
  267.      */
  268.  
  269.     anyProcesses = 0;
  270.     for (waitPtr = waitTable, count = waitTableUsed;
  271.         count > 0; waitPtr++, count--) {
  272.         for (i = 0; i < numPids; i++) {
  273.         if (pidPtr[i] != waitPtr->pid) {
  274.             continue;
  275.         }
  276.         anyProcesses = 1;
  277.         if (waitPtr->flags & WI_READY) {
  278.             *statusPtr = *((int *) &waitPtr->status);
  279.             pid = waitPtr->pid;
  280.             if (WIFEXITED(waitPtr->status)
  281.                 || WIFSIGNALED(waitPtr->status)) {
  282.             *waitPtr = waitTable[waitTableUsed-1];
  283.             waitTableUsed--;
  284.             } else {
  285.             waitPtr->flags &= ~WI_READY;
  286.             }
  287.             return pid;
  288.         }
  289.         }
  290.     }
  291.  
  292.     /*
  293.      * Make sure that the caller at least specified one valid
  294.      * process to wait for.
  295.      */
  296.  
  297.     if (!anyProcesses) {
  298.         errno = ECHILD;
  299.         return -1;
  300.     }
  301.  
  302.     /*
  303.      * Wait for a process to exit or suspend, then update its
  304.      * entry in the table and go back to the beginning of the
  305.      * loop to see if it's one of the desired processes.
  306.      */
  307.  
  308.     pid = wait(&status);
  309.     if (pid < 0) {
  310.         return pid;
  311.     }
  312.     for (waitPtr = waitTable, count = waitTableUsed; ;
  313.         waitPtr++, count--) {
  314.         if (count == 0) {
  315.         break;            /* Ignore unknown processes. */
  316.         }
  317.         if (pid != waitPtr->pid) {
  318.         continue;
  319.         }
  320.  
  321.         /*
  322.          * If the process has been detached, then ignore anything
  323.          * other than an exit, and drop the entry on exit.
  324.          */
  325.  
  326.         if (waitPtr->flags & WI_DETACHED) {
  327.         if (WIFEXITED(status) || WIFSIGNALED(status)) {
  328.             *waitPtr = waitTable[waitTableUsed-1];
  329.             waitTableUsed--;
  330.         }
  331.         } else {
  332.         waitPtr->status = status;
  333.         waitPtr->flags |= WI_READY;
  334.         }
  335.         break;
  336.     }
  337.     }
  338. }
  339.  
  340. /*
  341.  *----------------------------------------------------------------------
  342.  *
  343.  * Tcl_DetachPids --
  344.  *
  345.  *    This procedure is called to indicate that one or more child
  346.  *    processes have been placed in background and are no longer
  347.  *    cared about.  They should be ignored in future calls to
  348.  *    Tcl_WaitPids.
  349.  *
  350.  * Results:
  351.  *    None.
  352.  *
  353.  * Side effects:
  354.  *    None.
  355.  *
  356.  *----------------------------------------------------------------------
  357.  */
  358.  
  359. void
  360. Tcl_DetachPids(numPids, pidPtr)
  361.     int numPids;        /* Number of pids to detach:  gives size
  362.                  * of array pointed to by pidPtr. */
  363.     int *pidPtr;        /* Array of pids to detach:  must have
  364.                  * been created by Tcl_Fork. */
  365. {
  366.     register WaitInfo *waitPtr;
  367.     int i, count, pid;
  368.  
  369.     for (i = 0; i < numPids; i++) {
  370.     pid = pidPtr[i];
  371.     for (waitPtr = waitTable, count = waitTableUsed;
  372.         count > 0; waitPtr++, count--) {
  373.         if (pid != waitPtr->pid) {
  374.         continue;
  375.         }
  376.  
  377.         /*
  378.          * If the process has already exited then destroy its
  379.          * table entry now.
  380.          */
  381.  
  382.         if ((waitPtr->flags & WI_READY) && (WIFEXITED(waitPtr->status)
  383.             || WIFSIGNALED(waitPtr->status))) {
  384.         *waitPtr = waitTable[waitTableUsed-1];
  385.         waitTableUsed--;
  386.         } else {
  387.         waitPtr->flags |= WI_DETACHED;
  388.         }
  389.         goto nextPid;
  390.     }
  391.     panic("Tcl_Detach couldn't find process");
  392.  
  393.     nextPid:
  394.     continue;
  395.     }
  396. }
  397.  
  398. /*
  399.  *----------------------------------------------------------------------
  400.  *
  401.  * Tcl_CreatePipeline --
  402.  *
  403.  *    Given an argc/argv array, instantiate a pipeline of processes
  404.  *    as described by the argv.
  405.  *
  406.  * Results:
  407.  *    The return value is a count of the number of new processes
  408.  *    created, or -1 if an error occurred while creating the pipeline.
  409.  *    *pidArrayPtr is filled in with the address of a dynamically
  410.  *    allocated array giving the ids of all of the processes.  It
  411.  *    is up to the caller to free this array when it isn't needed
  412.  *    anymore.  If inPipePtr is non-NULL, *inPipePtr is filled in
  413.  *    with the file id for the input pipe for the pipeline (if any):
  414.  *    the caller must eventually close this file.  If outPipePtr
  415.  *    isn't NULL, then *outPipePtr is filled in with the file id
  416.  *    for the output pipe from the pipeline:  the caller must close
  417.  *    this file.  If errFilePtr isn't NULL, then *errFilePtr is filled
  418.  *    with a file id that may be used to read error output after the
  419.  *    pipeline completes.
  420.  *
  421.  * Side effects:
  422.  *    Processes and pipes are created.
  423.  *
  424.  *----------------------------------------------------------------------
  425.  */
  426.  
  427. int
  428. Tcl_CreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,
  429.     outPipePtr, errFilePtr)
  430.     Tcl_Interp *interp;        /* Interpreter to use for error reporting. */
  431.     int argc;            /* Number of entries in argv. */
  432.     char **argv;        /* Array of strings describing commands in
  433.                  * pipeline plus I/O redirection with <,
  434.                  * <<, and >.  Argv[argc] must be NULL. */
  435.     int **pidArrayPtr;        /* Word at *pidArrayPtr gets filled in with
  436.                  * address of array of pids for processes
  437.                  * in pipeline (first pid is first process
  438.                  * in pipeline). */
  439.     int *inPipePtr;        /* If non-NULL, input to the pipeline comes
  440.                  * from a pipe (unless overridden by
  441.                  * redirection in the command).  The file
  442.                  * id with which to write to this pipe is
  443.                  * stored at *inPipePtr.  -1 means command
  444.                  * specified its own input source. */
  445.     int *outPipePtr;        /* If non-NULL, output to the pipeline goes
  446.                  * to a pipe, unless overriden by redirection
  447.                  * in the command.  The file id with which to
  448.                  * read frome this pipe is stored at
  449.                  * *outPipePtr.  -1 means command specified
  450.                  * its own output sink. */
  451.     int *errFilePtr;        /* If non-NULL, all stderr output from the
  452.                  * pipeline will go to a temporary file
  453.                  * created here, and a descriptor to read
  454.                  * the file will be left at *errFilePtr.
  455.                  * The file will be removed already, so
  456.                  * closing this descriptor will be the end
  457.                  * of the file.  If this is NULL, then
  458.                  * all stderr output goes to our stderr. */
  459. {
  460.     int *pidPtr = NULL;        /* Points to malloc-ed array holding all
  461.                  * the pids of child processes. */
  462.     int numPids = 0;        /* Actual number of processes that exist
  463.                  * at *pidPtr right now. */
  464.     int cmdCount;        /* Count of number of distinct commands
  465.                  * found in argc/argv. */
  466.     char *input = NULL;        /* Describes input for pipeline, depending
  467.                  * on "inputFile".  NULL means take input
  468.                  * from stdin/pipe. */
  469.     int inputFile = 0;        /* Non-zero means input is name of input
  470.                  * file.  Zero means input holds actual
  471.                  * text to be input to command. */
  472.     char *output = NULL;    /* Holds name of output file to pipe to,
  473.                  * or NULL if output goes to stdout/pipe. */
  474.     int inputId = -1;        /* Readable file id input to current command in
  475.                  * pipeline (could be file or pipe).  -1
  476.                  * means use stdin. */
  477.     int outputId = -1;        /* Writable file id for output from current
  478.                  * command in pipeline (could be file or pipe).
  479.                  * -1 means use stdout. */
  480.     int errorId = -1;        /* Writable file id for all standard error
  481.                  * output from all commands in pipeline.  -1
  482.                  * means use stderr. */
  483.     int lastOutputId = -1;    /* Write file id for output from last command
  484.                  * in pipeline (could be file or pipe).
  485.                  * -1 means use stdout. */
  486.     int pipeIds[2];        /* File ids for pipe that's being created. */
  487.     int firstArg, lastArg;    /* Indexes of first and last arguments in
  488.                  * current command. */
  489.     int lastBar;
  490.     char *execName;
  491.     int i, j, pid;
  492.  
  493.     if (inPipePtr != NULL) {
  494.     *inPipePtr = -1;
  495.     }
  496.     if (outPipePtr != NULL) {
  497.     *outPipePtr = -1;
  498.     }
  499.     if (errFilePtr != NULL) {
  500.     *errFilePtr = -1;
  501.     }
  502.     pipeIds[0] = pipeIds[1] = -1;
  503.  
  504.     /*
  505.      * First, scan through all the arguments to figure out the structure
  506.      * of the pipeline.  Count the number of distinct processes (it's the
  507.      * number of "|" arguments).  If there are "<", "<<", or ">" arguments
  508.      * then make note of input and output redirection and remove these
  509.      * arguments and the arguments that follow them.
  510.      */
  511.  
  512.     cmdCount = 1;
  513.     lastBar = -1;
  514.     for (i = 0; i < argc; i++) {
  515.     if ((argv[i][0] == '|') && ((argv[i][1] == 0))) {
  516.         if ((i == (lastBar+1)) || (i == (argc-1))) {
  517.         interp->result = "illegal use of | in command";
  518.         return -1;
  519.         }
  520.         lastBar = i;
  521.         cmdCount++;
  522.         continue;
  523.     } else if (argv[i][0] == '<') {
  524.         if (argv[i][1] == 0) {
  525.         input = argv[i+1];
  526.         inputFile = 1;
  527.         } else if ((argv[i][1] == '<') && (argv[i][2] == 0)) {
  528.         input = argv[i+1];
  529.         inputFile = 0;
  530.         } else {
  531.         continue;
  532.         }
  533.     } else if ((argv[i][0] == '>') && (argv[i][1] == 0)) {
  534.         output = argv[i+1];
  535.     } else {
  536.         continue;
  537.     }
  538.     if (i >= (argc-1)) {
  539.         Tcl_AppendResult(interp, "can't specify \"", argv[i],
  540.             "\" as last word in command", (char *) NULL);
  541.         return -1;
  542.     }
  543.     for (j = i+2; j < argc; j++) {
  544.         argv[j-2] = argv[j];
  545.     }
  546.     argc -= 2;
  547.     i--;            /* Process new arg from same position. */
  548.     }
  549.     if (argc == 0) {
  550.     interp->result =  "didn't specify command to execute";
  551.     return -1;
  552.     }
  553.  
  554.     /*
  555.      * Set up the redirected input source for the pipeline, if
  556.      * so requested.
  557.      */
  558.  
  559.     if (input != NULL) {
  560.     if (!inputFile) {
  561.         /*
  562.          * Immediate data in command.  Create temporary file and
  563.          * put data into file.
  564.          */
  565.  
  566. #        define TMP_STDIN_NAME "/tmp/tcl.in.XXXXXX"
  567.         char inName[sizeof(TMP_STDIN_NAME) + 1];
  568.         int length;
  569.  
  570.         strcpy(inName, TMP_STDIN_NAME);
  571.         mktemp(inName);
  572.         inputId = open(inName, O_RDWR|O_CREAT|O_TRUNC, 0600);
  573.         if (inputId < 0) {
  574.         Tcl_AppendResult(interp,
  575.             "couldn't create input file for command: ",
  576.             Tcl_UnixError(interp), (char *) NULL);
  577.         goto error;
  578.         }
  579.         length = strlen(input);
  580.         if (write(inputId, input, length) != length) {
  581.         Tcl_AppendResult(interp,
  582.             "couldn't write file input for command: ",
  583.             Tcl_UnixError(interp), (char *) NULL);
  584.         goto error;
  585.         }
  586.         if ((lseek(inputId, 0L, 0) == -1) || (unlink(inName) == -1)) {
  587.         Tcl_AppendResult(interp,
  588.             "couldn't reset or remove input file for command: ",
  589.             Tcl_UnixError(interp), (char *) NULL);
  590.         goto error;
  591.         }
  592.     } else {
  593.         /*
  594.          * File redirection.  Just open the file.
  595.          */
  596.  
  597.         inputId = open(input, O_RDONLY, 0);
  598.         if (inputId < 0) {
  599.         Tcl_AppendResult(interp,
  600.             "couldn't read file \"", input, "\": ",
  601.             Tcl_UnixError(interp), (char *) NULL);
  602.         goto error;
  603.         }
  604.     }
  605.     } else if (inPipePtr != NULL) {
  606.     if (pipe(pipeIds) != 0) {
  607.         Tcl_AppendResult(interp,
  608.             "couldn't create input pipe for command: ",
  609.             Tcl_UnixError(interp), (char *) NULL);
  610.         goto error;
  611.     }
  612.     inputId = pipeIds[0];
  613.     *inPipePtr = pipeIds[1];
  614.     pipeIds[0] = pipeIds[1] = -1;
  615.     }
  616.  
  617.     /*
  618.      * Set up the redirected output sink for the pipeline from one
  619.      * of two places, if requested.
  620.      */
  621.  
  622.     if (output != NULL) {
  623.     /*
  624.      * Output is to go to a file.
  625.      */
  626.  
  627.     lastOutputId = open(output, O_WRONLY|O_CREAT|O_TRUNC, 0666);
  628.     if (lastOutputId < 0) {
  629.         Tcl_AppendResult(interp,
  630.             "couldn't write file \"", output, "\": ",
  631.             Tcl_UnixError(interp), (char *) NULL);
  632.         goto error;
  633.     }
  634.     } else if (outPipePtr != NULL) {
  635.     /*
  636.      * Output is to go to a pipe.
  637.      */
  638.  
  639.     if (pipe(pipeIds) != 0) {
  640.         Tcl_AppendResult(interp,
  641.             "couldn't create output pipe: ",
  642.             Tcl_UnixError(interp), (char *) NULL);
  643.         goto error;
  644.     }
  645.     lastOutputId = pipeIds[1];
  646.     *outPipePtr = pipeIds[0];
  647.     pipeIds[0] = pipeIds[1] = -1;
  648.     }
  649.  
  650.     /*
  651.      * Set up the standard error output sink for the pipeline, if
  652.      * requested.  Use a temporary file which is opened, then deleted.
  653.      * Could potentially just use pipe, but if it filled up it could
  654.      * cause the pipeline to deadlock:  we'd be waiting for processes
  655.      * to complete before reading stderr, and processes couldn't complete
  656.      * because stderr was backed up.
  657.      */
  658.  
  659.     if (errFilePtr != NULL) {
  660. #    define TMP_STDERR_NAME "/tmp/tcl.err.XXXXXX"
  661.     char errName[sizeof(TMP_STDERR_NAME) + 1];
  662.  
  663.     strcpy(errName, TMP_STDERR_NAME);
  664.     mktemp(errName);
  665.     errorId = open(errName, O_WRONLY|O_CREAT|O_TRUNC, 0600);
  666.     if (errorId < 0) {
  667.         errFileError:
  668.         Tcl_AppendResult(interp,
  669.             "couldn't create error file for command: ",
  670.             Tcl_UnixError(interp), (char *) NULL);
  671.         goto error;
  672.     }
  673.     *errFilePtr = open(errName, O_RDONLY, 0);
  674.     if (*errFilePtr < 0) {
  675.         goto errFileError;
  676.     }
  677.     if (unlink(errName) == -1) {
  678.         Tcl_AppendResult(interp,
  679.             "couldn't remove error file for command: ",
  680.             Tcl_UnixError(interp), (char *) NULL);
  681.         goto error;
  682.     }
  683.     }
  684.  
  685.     /*
  686.      * Scan through the argc array, forking off a process for each
  687.      * group of arguments between "|" arguments.
  688.      */
  689.  
  690.     pidPtr = (int *) ckalloc((unsigned) (cmdCount * sizeof(int)));
  691.     for (i = 0; i < numPids; i++) {
  692.     pidPtr[i] = -1;
  693.     }
  694.     for (firstArg = 0; firstArg < argc; numPids++, firstArg = lastArg+1) {
  695.     for (lastArg = firstArg; lastArg < argc; lastArg++) {
  696.         if ((argv[lastArg][0] == '|') && (argv[lastArg][1] == 0)) {
  697.         break;
  698.         }
  699.     }
  700.     argv[lastArg] = NULL;
  701.     if (lastArg == argc) {
  702.         outputId = lastOutputId;
  703.     } else {
  704.         if (pipe(pipeIds) != 0) {
  705.         Tcl_AppendResult(interp, "couldn't create pipe: ",
  706.             Tcl_UnixError(interp), (char *) NULL);
  707.         goto error;
  708.         }
  709.         outputId = pipeIds[1];
  710.     }
  711.     execName = Tcl_TildeSubst(interp, argv[firstArg]);
  712.     pid = Tcl_Fork();
  713.     if (pid == -1) {
  714.         Tcl_AppendResult(interp, "couldn't fork child process: ",
  715.             Tcl_UnixError(interp), (char *) NULL);
  716.         goto error;
  717.     }
  718.     if (pid == 0) {
  719.         char errSpace[200];
  720.  
  721.         if (((inputId != -1) && (dup2(inputId, 0) == -1))
  722.             || ((outputId != -1) && (dup2(outputId, 1) == -1))
  723.             || ((errorId != -1) && (dup2(errorId, 2) == -1))) {
  724.         char *err;
  725.         err = "forked process couldn't set up input/output\n";
  726.         write(errorId < 0 ? 2 : errorId, err, strlen(err));
  727.         _exit(1);
  728.         }
  729.         for (i = 3; (i <= outputId) || (i <= inputId) || (i <= errorId);
  730.             i++) {
  731.         close(i);
  732.         }
  733.         execvp(execName, &argv[firstArg]);
  734.         sprintf(errSpace, "couldn't find \"%.150s\" to execute\n",
  735.             argv[firstArg]);
  736.         write(2, errSpace, strlen(errSpace));
  737.         _exit(1);
  738.     } else {
  739.         pidPtr[numPids] = pid;
  740.     }
  741.  
  742.     /*
  743.      * Close off our copies of file descriptors that were set up for
  744.      * this child, then set up the input for the next child.
  745.      */
  746.  
  747.     if (inputId != -1) {
  748.         close(inputId);
  749.     }
  750.     if (outputId != -1) {
  751.         close(outputId);
  752.     }
  753.     inputId = pipeIds[0];
  754.     pipeIds[0] = pipeIds[1] = -1;
  755.     }
  756.     *pidArrayPtr = pidPtr;
  757.  
  758.     /*
  759.      * All done.  Cleanup open files lying around and then return.
  760.      */
  761.  
  762. cleanup:
  763.     if (inputId != -1) {
  764.     close(inputId);
  765.     }
  766.     if (lastOutputId != -1) {
  767.     close(lastOutputId);
  768.     }
  769.     if (errorId != -1) {
  770.     close(errorId);
  771.     }
  772.     return numPids;
  773.  
  774.     /*
  775.      * An error occurred.  There could have been extra files open, such
  776.      * as pipes between children.  Clean them all up.  Detach any child
  777.      * processes that have been created.
  778.      */
  779.  
  780.     error:
  781.     if ((inPipePtr != NULL) && (*inPipePtr != -1)) {
  782.     close(*inPipePtr);
  783.     *inPipePtr = -1;
  784.     }
  785.     if ((outPipePtr != NULL) && (*outPipePtr != -1)) {
  786.     close(*outPipePtr);
  787.     *outPipePtr = -1;
  788.     }
  789.     if ((errFilePtr != NULL) && (*errFilePtr != -1)) {
  790.     close(*errFilePtr);
  791.     *errFilePtr = -1;
  792.     }
  793.     if (pipeIds[0] != -1) {
  794.     close(pipeIds[0]);
  795.     }
  796.     if (pipeIds[1] != -1) {
  797.     close(pipeIds[1]);
  798.     }
  799.     if (pidPtr != NULL) {
  800.     for (i = 0; i < numPids; i++) {
  801.         if (pidPtr[i] != -1) {
  802.         Tcl_DetachPids(1, &pidPtr[i]);
  803.         }
  804.     }
  805.     ckfree((char *) pidPtr);
  806.     }
  807.     numPids = -1;
  808.     goto cleanup;
  809. }
  810.  
  811. /*
  812.  *----------------------------------------------------------------------
  813.  *
  814.  * Tcl_UnixError --
  815.  *
  816.  *    This procedure is typically called after UNIX kernel calls
  817.  *    return errors.  It stores machine-readable information about
  818.  *    the error in $errorCode returns an information string for
  819.  *    the caller's use.
  820.  *
  821.  * Results:
  822.  *    The return value is a human-readable string describing the
  823.  *    error, as returned by strerror.
  824.  *
  825.  * Side effects:
  826.  *    The global variable $errorCode is reset.
  827.  *
  828.  *----------------------------------------------------------------------
  829.  */
  830.  
  831. char *
  832. Tcl_UnixError(interp)
  833.     Tcl_Interp *interp;        /* Interpreter whose $errorCode variable
  834.                  * is to be changed. */
  835. {
  836.     char *id, *msg;
  837.  
  838.     id = Tcl_ErrnoId();
  839.     msg = strerror(errno);
  840.     Tcl_SetErrorCode(interp, "UNIX", id, msg, (char *) NULL);
  841.     return msg;
  842. }
  843.  
  844. /*
  845.  *----------------------------------------------------------------------
  846.  *
  847.  * TclMakeFileTable --
  848.  *
  849.  *    Create or enlarge the file table for the interpreter, so that
  850.  *    there is room for a given index.
  851.  *
  852.  * Results:
  853.  *    None.
  854.  *
  855.  * Side effects:
  856.  *    The file table for iPtr will be created if it doesn't exist
  857.  *    (and entries will be added for stdin, stdout, and stderr).
  858.  *    If it already exists, then it will be grown if necessary.
  859.  *
  860.  *----------------------------------------------------------------------
  861.  */
  862.  
  863. void
  864. TclMakeFileTable(iPtr, index)
  865.     Interp *iPtr;        /* Interpreter whose table of files is
  866.                  * to be manipulated. */
  867.     int index;            /* Make sure table is large enough to
  868.                  * hold at least this index. */
  869. {
  870.     /*
  871.      * If the table doesn't even exist, then create it and initialize
  872.      * entries for standard files.
  873.      */
  874.  
  875.     if (iPtr->numFiles == 0) {
  876.     OpenFile *filePtr;
  877.     int i;
  878.  
  879.     if (index < 2) {
  880.         iPtr->numFiles = 3;
  881.     } else {
  882.         iPtr->numFiles = index+1;
  883.     }
  884.     iPtr->filePtrArray = (OpenFile **) ckalloc((unsigned)
  885.         ((iPtr->numFiles)*sizeof(OpenFile *)));
  886.     for (i = iPtr->numFiles-1; i >= 0; i--) {
  887.         iPtr->filePtrArray[i] = NULL;
  888.     }
  889.  
  890.     filePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
  891.     filePtr->f = stdin;
  892.     filePtr->f2 = NULL;
  893.     filePtr->readable = 1;
  894.     filePtr->writable = 0;
  895.     filePtr->numPids = 0;
  896.     filePtr->pidPtr = NULL;
  897.     filePtr->errorId = -1;
  898.     iPtr->filePtrArray[0] = filePtr;
  899.  
  900.     filePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
  901.     filePtr->f = stdout;
  902.     filePtr->f2 = NULL;
  903.     filePtr->readable = 0;
  904.     filePtr->writable = 1;
  905.     filePtr->numPids = 0;
  906.     filePtr->pidPtr = NULL;
  907.     filePtr->errorId = -1;
  908.     iPtr->filePtrArray[1] = filePtr;
  909.  
  910.     filePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
  911.     filePtr->f = stderr;
  912.     filePtr->f2 = NULL;
  913.     filePtr->readable = 0;
  914.     filePtr->writable = 1;
  915.     filePtr->numPids = 0;
  916.     filePtr->pidPtr = NULL;
  917.     filePtr->errorId = -1;
  918.     iPtr->filePtrArray[2] = filePtr;
  919.     } else if (index >= iPtr->numFiles) {
  920.     int newSize;
  921.     OpenFile **newPtrArray;
  922.     int i;
  923.  
  924.     newSize = index+1;
  925.     newPtrArray = (OpenFile **) ckalloc((unsigned)
  926.         ((newSize)*sizeof(OpenFile *)));
  927.     memcpy((VOID *) newPtrArray, (VOID *) iPtr->filePtrArray,
  928.         iPtr->numFiles*sizeof(OpenFile *));
  929.     for (i = iPtr->numFiles; i < newSize; i++) {
  930.         newPtrArray[i] = NULL;
  931.     }
  932.     ckfree((char *) iPtr->filePtrArray);
  933.     iPtr->numFiles = newSize;
  934.     iPtr->filePtrArray = newPtrArray;
  935.     }
  936. }
  937.  
  938. /*
  939.  *----------------------------------------------------------------------
  940.  *
  941.  * TclGetOpenFile --
  942.  *
  943.  *    Given a string identifier for an open file, find the corresponding
  944.  *    open file structure, if there is one.
  945.  *
  946.  * Results:
  947.  *    A standard Tcl return value.  If the open file is successfully
  948.  *    located, *filePtrPtr is modified to point to its structure.
  949.  *    If TCL_ERROR is returned then interp->result contains an error
  950.  *    message.
  951.  *
  952.  * Side effects:
  953.  *    None.
  954.  *
  955.  *----------------------------------------------------------------------
  956.  */
  957.  
  958. int
  959. TclGetOpenFile(interp, string, filePtrPtr)
  960.     Tcl_Interp *interp;        /* Interpreter in which to find file. */
  961.     char *string;        /* String that identifies file. */
  962.     OpenFile **filePtrPtr;    /* Address of word in which to store pointer
  963.                  * to structure about open file. */
  964. {
  965.     int fd = 0;            /* Initial value needed only to stop compiler
  966.                  * warnings. */
  967.     Interp *iPtr = (Interp *) interp;
  968.  
  969.     if ((string[0] == 'f') && (string[1] == 'i') && (string[2] == 'l')
  970.         & (string[3] == 'e')) {
  971.     char *end;
  972.  
  973.     fd = strtoul(string+4, &end, 10);
  974.     if ((end == string+4) || (*end != 0)) {
  975.         goto badId;
  976.     }
  977.     } else if ((string[0] == 's') && (string[1] == 't')
  978.         && (string[2] == 'd')) {
  979.     if (strcmp(string+3, "in") == 0) {
  980.         fd = 0;
  981.     } else if (strcmp(string+3, "out") == 0) {
  982.         fd = 1;
  983.     } else if (strcmp(string+3, "err") == 0) {
  984.         fd = 2;
  985.     } else {
  986.         goto badId;
  987.     }
  988.     } else {
  989.     badId:
  990.     Tcl_AppendResult(interp, "bad file identifier \"", string,
  991.         "\"", (char *) NULL);
  992.     return TCL_ERROR;
  993.     }
  994.  
  995.     if (fd >= iPtr->numFiles) {
  996.     if ((iPtr->numFiles == 0) && (fd <= 2)) {
  997.         TclMakeFileTable(iPtr, fd);
  998.     } else {
  999.         notOpen:
  1000.         Tcl_AppendResult(interp, "file \"", string, "\" isn't open",
  1001.             (char *) NULL);
  1002.         return TCL_ERROR;
  1003.     }
  1004.     }
  1005.     if (iPtr->filePtrArray[fd] == NULL) {
  1006.     goto notOpen;
  1007.     }
  1008.     *filePtrPtr = iPtr->filePtrArray[fd];
  1009.     return TCL_OK;
  1010. }
  1011.